package com.wjk.kylin.rpc.core.invoker.route;

import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * 请求负载均衡算法
 *
 * @author wjk
 */
public interface LoadBalance {

    /**
     * 获取路由地址
     *
     * @param serviceKey
     * @param addressSet
     * @return
     */
    String route(String serviceKey, TreeSet<String> addressSet);

    enum Route implements LoadBalance {

        /**
         * 随机
         */
        Random {
            private final Random random = new Random();

            @Override
            public String route(String serviceKey, TreeSet<String> addressSet) {
                if (CollectionUtils.isEmpty(addressSet)) {
                    return null;
                }
                //随机
                String[] addressArr = addressSet.toArray(new String[0]);
                // round
                return addressArr[random.nextInt(addressArr.length)];
            }
        },
        /**
         * 轮询
         */
        Round {
            private final ConcurrentMap<String, Integer> routeCount = new ConcurrentHashMap<>();
            private long cacheValidTime = 0;

            @Override
            public String route(String serviceKey, TreeSet<String> addressSet) {
                if (CollectionUtils.isEmpty(addressSet)) {
                    return null;
                }
                // cache clear
                if (System.currentTimeMillis() > cacheValidTime) {
                    routeCount.clear();
                    cacheValidTime = System.currentTimeMillis() + 24 * 60 * 60 * 100;
                }

                // count++
                Integer count = routeCount.get(serviceKey);
                // 初始化时主动Random一次，缓解首次压力
                count = (count == null || count >= Integer.MAX_VALUE) ? (new Random().nextInt(100)) : ++count;
                routeCount.put(serviceKey, count);

                String[] addressArr = addressSet.toArray(new String[0]);
                // round
                return addressArr[count % addressArr.length];
            }
        },
        /**
         * 近期最少使用算法 | 最近最久未使用
         * 最近被访问的数据那么它将来访问的概率就大，缓存满的时候，优先淘汰最无人问津者
         * 基于按访问顺序存储的方式实现LRU
         */
        Lru {
            private final ConcurrentMap<String, LinkedHashMap<String, String>> routeCount = new ConcurrentHashMap<>();
            private long cacheValidTime = 0;

            @Override
            public String route(String serviceKey, TreeSet<String> addressSet) {
                if (CollectionUtils.isEmpty(addressSet)) {
                    return null;
                }
                // cache clear
                if (System.currentTimeMillis() > cacheValidTime) {
                    routeCount.clear();
                    cacheValidTime = System.currentTimeMillis() + 24 * 60 * 60 * 100;
                }

                // init lru
                LinkedHashMap<String, String> lruItem = routeCount.get(serviceKey);
                if (lruItem == null) {
                    /**
                     * LinkedHashMap
                     *      a、accessOrder：ture=访问顺序排序（get/put时排序）/ACCESS-LAST；false=插入顺序排期/FIFO；
                     *      b、removeEldestEntry：新增元素时将会调用，返回true时会删除最老元素；可封装LinkedHashMap并重写该方法，比如定义最大容量，超出是返回true即可实现固定长度的LRU算法；
                     */
                    lruItem = new LinkedHashMap<String, String>(16, 0.75f, true) {
                        @Override
                        protected boolean removeEldestEntry(Map.Entry<String, String> eldest) {
                            return super.size() > 1000;
                        }
                    };
                    routeCount.putIfAbsent(serviceKey, lruItem);
                }

                // put new
                for (String address : addressSet) {
                    if (!lruItem.containsKey(address)) {
                        lruItem.put(address, address);
                    }
                }
                // remove old
                List<String> delKeys = new ArrayList<>();
                for (String existKey : lruItem.keySet()) {
                    if (!addressSet.contains(existKey)) {
                        delKeys.add(existKey);
                    }
                }
                if (delKeys.size() > 0) {
                    for (String delKey : delKeys) {
                        lruItem.remove(delKey);
                    }
                }
                // load
                String eldestKey = lruItem.entrySet().iterator().next().getKey();
                return lruItem.get(eldestKey);
            }
        },
        /**
         * LFU（Least Frequently Used） 最不经常使用缓存算法。
         */
        Lfu {
            private final ConcurrentMap<String, HashMap<String, Integer>> routeCount = new ConcurrentHashMap<>();
            private long cacheValidTime = 0;

            @Override
            public String route(String serviceKey, TreeSet<String> addressSet) {
                if (CollectionUtils.isEmpty(addressSet)) {
                    return null;
                }
                // cache clear
                if (System.currentTimeMillis() > cacheValidTime) {
                    routeCount.clear();
                    cacheValidTime = System.currentTimeMillis() + 24 * 60 * 60 * 100;
                }
                // lfu item init
                // Key排序可以用TreeMap+构造入参Compare；Value排序暂时只能通过ArrayList；
                HashMap<String, Integer> lfuItemMap = routeCount.get(serviceKey);
                if (lfuItemMap == null) {
                    lfuItemMap = new HashMap<>(16);
                    // 避免重复覆盖
                    routeCount.putIfAbsent(serviceKey, lfuItemMap);
                }

                // put new
                for (String address : addressSet) {
                    if (!lfuItemMap.containsKey(address) || lfuItemMap.get(address) >= Integer.MAX_VALUE) {
                        lfuItemMap.put(address, 0);
                    }
                }

                // remove old
                List<String> delKeys = new ArrayList<>();
                for (String existKey : lfuItemMap.keySet()) {
                    if (!addressSet.contains(existKey)) {
                        delKeys.add(existKey);
                    }
                }
                if (delKeys.size() > 0) {
                    for (String delKey : delKeys) {
                        lfuItemMap.remove(delKey);
                    }
                }

                // load least userd count address
                List<Map.Entry<String, Integer>> lfuItemList = new ArrayList<>(lfuItemMap.entrySet());
                lfuItemList.sort(Map.Entry.comparingByValue());

                Map.Entry<String, Integer> addressItem = lfuItemList.get(0);
                String minAddress = addressItem.getKey();
                addressItem.setValue(addressItem.getValue() + 1);

                return minAddress;
            }
        }

    }
}
